package part4

import (
	"bytes"
	"crypto/sha256"
	"encoding/gob"
	"encoding/hex"
	"fmt"
	"log"
	"strings"
)

//挖矿奖励
const subsidy = 10

//交易
type Transaction struct {
	ID   []byte     //交易hash值 = hash(vin + vout)
	Vin  []TXInput  //输入
	Vout []TXOutput //输出
}
type TXOutput struct {
	Value        int    //多少值，就是多少比特币
	ScriptPubKey string //解锁的脚本，我们目前暂时就当作收货人地址就行
}

type TXInput struct {
	Txid      []byte //之前的交易hash值
	Vout      int    //是之前交易的第几个output
	ScriptSig string //解锁脚本，目前暂时当作发货人的发货地址
}

//新建一个挖矿奖励交易
func NewCoinbaseTX(to, data string) Transaction {
	if data == "" {
		data = fmt.Sprintf("Reward to '%s'", to)
	}
	//Coinbas的input的hash为空，不需要
	txin := TXInput{[]byte{}, -1, data}
	//subsidy奖励
	txout := TXOutput{subsidy, to}
	//组成一个交易
	tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
	tx.ID = tx.Hash()

	return tx
}

//新建交易，发送币
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) Transaction {
	var inputs []TXInput
	var outputs []TXOutput

	acc, allValidOutputIndex, unspentOutputMap := bc.FindSpendableOutputs(from)

	if acc < amount {
		log.Panic("ERROR: Not enough funds")
	}

	//找到足够的output
	validOutputs := make(map[string][]int)
	accumulated := 0
	for txID, outIndexList := range allValidOutputIndex {
		for _, outIndex := range outIndexList {
			if accumulated < amount {
				out := unspentOutputMap[txID][outIndex]
				accumulated += out.Value
				validOutputs[txID] = append(validOutputs[txID], outIndex)
			}
		}
	}

	// 构建input，把找到的input的打个包inputs
	for txid, outs := range validOutputs {
		txID, _ := hex.DecodeString(txid)

		for _, out := range outs {
			input := TXInput{txID, out, from}
			inputs = append(inputs, input)
		}
	}

	//构建output
	outputs = append(outputs, TXOutput{amount, to})
	if acc > amount {
		//如果需要找零，新建一个output
		outputs = append(outputs, TXOutput{acc - amount, from})
	}

	tx := Transaction{nil, inputs, outputs}
	tx.ID = tx.Hash()

	return tx
}

//是否是coinbase交易
func (tx Transaction) IsCoinbase() bool {
	return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1
}

//hash 交易
func (tx *Transaction) Hash() []byte {
	var hash [32]byte

	txCopy := *tx
	txCopy.ID = []byte{}
	hash = sha256.Sum256(txCopy.Serialize())

	return hash[:]
}

//序列化Transaction
func (tx Transaction) Serialize() []byte {
	var encoded bytes.Buffer

	enc := gob.NewEncoder(&encoded)
	err := enc.Encode(tx)
	if err != nil {
		log.Panic(err)
	}

	return encoded.Bytes()
}

//打印交易
func (tx Transaction) String() string {
	var lines []string

	lines = append(lines, fmt.Sprintf("----Transaction %x:", tx.ID))

	for i, input := range tx.Vin {

		lines = append(lines, fmt.Sprintf("    Input[%d]", i))
		lines = append(lines, fmt.Sprintf("       TXID:      %x", input.Txid))
		lines = append(lines, fmt.Sprintf("       Out:       %d", input.Vout))
		lines = append(lines, fmt.Sprintf("       Signature: %s", input.ScriptSig))
	}

	for i, output := range tx.Vout {
		lines = append(lines, fmt.Sprintf("    Output[%d]", i))
		lines = append(lines, fmt.Sprintf("       Value:  %d", output.Value))
		lines = append(lines, fmt.Sprintf("       Script: %s", output.ScriptPubKey))
	}

	return strings.Join(lines, "\n")
}
